package com.nulldev.util.net.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.regex.Pattern;

public abstract class UDPClient extends Thread implements Runnable {
	private InetAddress ip;
	private DatagramSocket socket;
	private int maxDataSize, port;
	private int tries;
	private float tickRate, oldtickRate;
	public boolean replied;
	
	
//	/**
//	 * 
//	 * @param ipAddress (Server IP goes here)
//	 * @param listenRate (# of times listens in a second, to calculate -> (1000 / (tickrate / 2)))
//	 * (note: for most updates and stablity use a listenRate of 16 for 125 updates a second.)
//	 * @param maxDataSize (Maximum data size in length) -> affects inbound
//	 * @throws Exception (you know what this is)
//	 */
//	public Client(String ipAddress, int listenRate, int maxDataSize) throws Exception{
//		setName("nullUtil 2.0 Client");
//		this.port = port;
//		this.socket = new DatagramSocket();
//		this.ip = InetAddress.getByName(ipAddress);
//		this.tickRate = listenRate;
//		this.maxDataSize = maxDataSize;
//	}
	
	/**
	 * Possible bad method
	 * @param ipAddress (Server IP goes here)
	 * @param port (Server Port goes here)
	 * @param listenRate (# of times listens in a second, to calculate -> (1000 / (tickrate / 2))) <br>
	 * (note: for most updates and stablity use a listenRate of 16 for 125 updates a second.)
	 * @param maxDataSize (Maximum data size in length) -> affects inbound
	 * @throws Exception (you know what this is)
	 */
	public UDPClient(String ipAddress, int port, int listenRate, int maxDataSize) throws Exception{
		setName("nullUtil 2.0 Client");
		this.port = port;
		this.socket = new DatagramSocket();
		this.ip = InetAddress.getByName(ipAddress);
		this.tickRate = this.oldtickRate = (1000 / listenRate);
		this.maxDataSize = maxDataSize;
	}
	
	private boolean stopped;
	
	/**
	 * Listener 
	 * Listens from any incoming data, add own method to handle
	 */
	@Override
	public void run() {
		while (true) {
			if (this.stopped || this.socket.isClosed()) {
				break;
			}
			try{
				byte[] data = new byte[this.maxDataSize];
				DatagramPacket packet = new DatagramPacket(data, data.length);
				this.socket.setSoTimeout((int) tickRate);
				this.socket.receive(packet);
				this.handle(packet);
				this.sleep((long)tickRate);
			}catch (Exception e){
				if (e instanceof SocketTimeoutException || e instanceof SocketException){	
				}else{
					e.printStackTrace();
				}
			}
		}
	}
	
	public void reply(byte[] data) throws Exception{
		if (data.length > this.maxDataSize){
			throw new Exception("Data packet larger than maximum! (dataSize='" + data.length + "', max='" + this.maxDataSize + "')");
		}
		DatagramPacket packet = new DatagramPacket(data, data.length, ip, port);
		this.socket.send(packet);
	}
	
	public void reply(String text) throws Exception {
		if (text.getBytes("UTF-16LE").length > this.maxDataSize){
			throw new Exception("Data packet larger than maximum! (dataSize='" + text.getBytes("UTF-16LE").length + "', max='" + this.maxDataSize + "')");
		}
		final byte[] data = text.getBytes("UTF-16LE");
		DatagramPacket packet = new DatagramPacket(data, data.length, ip, port);
		this.socket.send(packet);
	}
	
	public void reply_unencrypted(String text) throws Exception{
		if (text.getBytes().length > this.maxDataSize){
			throw new Exception("Data packet larger than maximum! (dataSize='" + text.getBytes("UTF-16LE").length + "', max='" + this.maxDataSize + "')");
		}
		final byte[] data = text.getBytes();
		DatagramPacket packet = new DatagramPacket(data, data.length, ip, port);
		this.socket.send(packet);
	}
	
	
	public void handle(DatagramPacket p) throws Exception{
		final String data = convertToString(p.getData()).trim().replaceAll(Pattern.quote("%space"), " ");
		//System.out.println("IN -> " + data.length());
		//System.out.println("Client IN -> '" + (data.length() > 64 ? data.length() : data) + "'");
		handle_EXT(data, p);
		handleByte(p.getData(), p);
	}

	public final Charset UTF8_CHARSET = Charset.forName("UTF-16LE");
	public abstract void handle_EXT(String data, DatagramPacket p) throws Exception;
	
	public void handleByte(byte[] data, DatagramPacket p) throws Exception {}
	
	public String convertToString(byte[] data){
		return new String(data, UTF8_CHARSET);
	}

	public void setTickRate(float rate){
		System.out.println("CLIENT | Server changed tickrate to " + (1000 / rate) + ".");
		this.tickRate = rate;
	}
	
	public void setDataSize(int size){
		System.out.println("CLIENT | Server changed dataSize to " + size + ".");
		this.maxDataSize = size;
	}
	
	public void setNewAddress(String ip, int port) throws Exception{
		this.ip = InetAddress.getByName(ip);
		this.port = port;
		replied = false;
		tries = 0;
	}
	
	public void setNewAddress_NOPING(String ip, int port) throws Exception{
		this.ip = InetAddress.getByName(ip);
		this.port = port;
	}

	public int getTickRate() {
		return (int) this.tickRate;
	}
	
	public int getBetterTickRate(){
		return (int) (1000 / this.tickRate);
	}
	
	public DatagramSocket i(){
		return this.socket;
	}

	public void close() {
		this.stopped = true;
		this.socket.close();
	}
}
